/*  ----------------------------------------------------------------------------
 *  Arello Mobile
 *  Fast 3d Engine
 *  ----------------------------------------------------------------------------
 * Except where otherwise noted, this work is licensed under
 * http://creativecommons.org/licenses/by/3.0
 */

#include "TextureSequence.h"
#include <lib3d/hard/Hardware.h>
#include <xml/Stream.h>
#include <Color.h>
#include <lib3d/hard/Texture.h>
#include <math/utl.h>
#include <safe_new.h>

namespace liba {
namespace lib3d {
namespace material {

bool TextureSequenceName::operator<( const TextureSequenceName & na )const
{
	if(image != na.image)
		return image < na.image;

	if(frame_start != na.frame_start)
		return frame_start < na.frame_start;

	return frame_end < na.frame_end;
}

bool TextureSequenceFrameRep::Name::operator<( const TextureSequenceFrameRep::Name & na )const
{
	if(image != na.image)
		return image < na.image;

	return frame_number < na.frame_number;
}

TextureSequenceFrameRep::TextureSequenceFrameRep(const Name & name)
:	pl( 0 ),
	alpha_channel( 0 )
{
	if( !pl )
		pl = name.parent->get_loader(name.image);
	hard::Manager::add_dependent( this );
}

TextureSequenceFrameRep::~TextureSequenceFrameRep()
{
	hard::Manager::remove_dependent( this );
}

void TextureSequenceFrameRep::load(const TextureSequenceFrameRep::Name & name)
{
	if( !pl )
		pl = name.parent->get_loader(name.image);
	if( !pl->is_ready() )
		return;
	if( !pl->is_valid() )
		THROW(GluckBadResource( __FUNCTION__, "pl is not valid", name.image ));

	alpha_channel = pictureformat::GetAlphaBits( pl->get_best_format() ) != 0;

	HardVector::iterator it = hard_vector.begin();
	for( ;it != hard_vector.end(); ++it )
	{
		if(!it->first->is_operational())
			continue;

		int frames_count = name.total_frames_count;
		int frame_height = pl->get_height();
		int frame_width = pl->get_width()/frames_count;

		int mipmap_levels = material::Texture::get_mipmap_levels( pl );
		if( !it->second )
			it->second = it->first->create_texture( mipmap_levels, pl->get_best_format(), frame_width, frame_height );

		it->second->set_wrap(name.wrap);

		if( !it->second->is_valid() )
		{
			// can be created less mipmaps, then requested
			for( int m = 0; m != it->second->get_mipmap_levels(); ++m )
			{
				int current_frame_shift = name.frame_number * frame_width * 4;

				std::vector<char> colors;
				colors.reserve(4*pl->get_width(m));
				pictureformat::Format hardware_format = it->second->get_hardware_format();

				int stride = 0;
				void * dst = it->second->lock(m, &stride);
				if( !dst )
					continue;
				pl->reset( pictureformat::A8R8G8B8, m );
				// Load data from pl
				for( int i = 0; i != pl->get_height(m); ++i )
				{
					pl->unpack_scanline(&colors.front());

					pictureformat::convert_scanline<pictureformat::A8R8G8B8>(hardware_format, &colors.front()+current_frame_shift, dst, frame_width);
					dst = reinterpret_cast<char *>(dst) + stride;
				}
				it->second->unlock(m);
			}
		}
	}
	name.parent->release_loader();
	stop_loading();
}

void TextureSequenceFrameRep::render_to_list( hard::Hardware * hardware, hard::RenderTexture * render_texture )
{
	hard::Texture * tex = find_data( hardware );
	if( !tex || !tex->is_valid() )
	{
		start_loading();
		return;
	}
	
	render_texture->texture = tex;
	render_texture->alpha_channel = alpha_channel;
}

void TextureSequenceFrameRep::on_add_hardware(hard::Manager::hardware_type * ha)
{
	hard_vector.push_back( SweetPair(ha, (liba::lib3d::hard::Texture*)0) );
	start_loading();
}

TextureSequenceFrames::TextureSequenceFrames(const TextureSequenceName & name)
:	loader_ref_count(0)
{
	pl.reset(new pictureformat::Loader(name.image));
	start_loading();
}

void TextureSequenceFrames::load(const TextureSequenceName & name)
{
	if(!pl)
		return;
	if( !pl->is_ready() )
		return;
	if( !pl->is_valid() )
		THROW(GluckBadResource( __FUNCTION__, "pl is not valid", name.image ));

	int width = pl->get_width();
	int height = pl->get_height();

	int frames_count = name.frame_end - name.frame_start;
	int frame_start = name.frame_start;

	//load all frames
	if(!frames_count)
	{
		frames_count = name.total_frames_count - 1;
		frame_start = 0;
	}

	int dir = math::sgn(frames_count);
	frames_count = math::abs(frames_count);

	for(int i = frame_start; frames_count >= 0; i += dir, --frames_count)
	{
		TextureSequenceFrameRep::Name rep_name(this, name.image, i, name.total_frames_count);
		rep_name.wrap = name.wrap;
		push_back(new TextureSequenceFrame(rep_name));
	}
	stop_loading();
}

pictureformat::Loader * TextureSequenceFrames::get_loader(const filesystem::String & image)
{
	if(!pl)
		pl.reset(new pictureformat::Loader(image));

	++loader_ref_count;
	return pl.get();
}

void TextureSequenceFrames::release_loader()
{
	--loader_ref_count;
	if(loader_ref_count < 0)
		loader_ref_count = 0;

	if(!loader_ref_count)
		pl.reset();
}

TextureSequence::TextureSequence(const Atom & animate_node_name, const TextureSequenceName & name)
:	Texture(animate_node_name, name.wrap),
	current_frame(0)
{
	load_rep(name);
}

TextureSequence * TextureSequence::clone()const
{
	return new TextureSequence(*this);
}

} // namespace material
} // namespace lib3d
} // namespace liba

